Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lldb][DWARF] Delay struct/class/union definition DIE searching when parsing declaration DIEs. #90663

Merged
merged 11 commits into from
May 10, 2024

Conversation

ZequanWu
Copy link
Contributor

@ZequanWu ZequanWu commented Apr 30, 2024

This is the implementation for https://discourse.llvm.org/t/rfc-delay-definition-die-searching-when-parse-a-declaration-die-for-record-type/78526.

Motivation

Currently, lldb eagerly searches for definition DIE when parsing a declaration DIE for struct/class/union definition DIE. It will search for all definition DIEs with the same unqualified name (just DW_AT_name ) and then find out those DIEs with same fully qualified name. Then lldb will try to resolve those DIEs to create the Types from definition DIEs. It works fine most time. However, when built with -gsimple-template-names, the search graph expands very quickly, because for the specialized-template classes, they don’t have template parameter names encoded inside DW_AT_name. They have DW_TAG_template_type_parameter to reference the types used as template parameters. In order to identify if a definition DIE matches a declaration DIE, lldb needs to resolve all template parameter types first and those template parameter types might be template classes as well, and so on… So, the search graph explodes, causing a lot unnecessary searching/type-resolving to just get the fully qualified names for a specialized-template class. This causes lldb stack overflow for us internally on template-heavy libraries.

Implementation

Instead of searching for definition DIEs when parsing declaration DIEs, we always construct the record type from the DIE regardless if it's definition or declaration. The process of searching for definition DIE is refactored to DWARFASTParserClang::FindDefinitionTypeForDIE which is invoked when 1) completing the type on SymbolFileDWARF::CompleteType. 2) the record type needs to start its definition as a containing type so that nested classes can be added into it in PrepareContextToReceiveMembers.

The key difference is SymbolFileDWARF::ResolveType return a Type* that might be created from declaration DIE, which means it hasn't starts its definition yet. We also need to change according in places where we want the type to start definition, like PrepareContextToReceiveMembers (I'm not aware of any other places, but this should be a simple call to SymbolFileDWARF::FindDefinitionDIE)

Result

It fixes the stack overflow of lldb for the internal binary built with simple template name. When constructing the fully qualified name built with -gsimple-template-names, it gets the name of the type parameter by resolving the referenced DIE, which might be a declaration (we won't try to search for the definition DIE to just get the name).
I got rough measurement about the time using the same commands (set breakpoint, run, expr this, exit). For the binary built without -gsimple-template-names, this change has no impact on time, still taking 41 seconds to complete. When built with -gsimple-template-names, it also takes about 41 seconds to complete wit this change.

@ZequanWu ZequanWu marked this pull request as ready for review April 30, 2024 20:56
@ZequanWu ZequanWu requested a review from JDevlieghere as a code owner April 30, 2024 20:56
@llvmbot llvmbot added the lldb label Apr 30, 2024
@ZequanWu ZequanWu requested a review from Michael137 April 30, 2024 20:56
@llvmbot
Copy link
Member

llvmbot commented Apr 30, 2024

@llvm/pr-subscribers-lldb

Author: Zequan Wu (ZequanWu)

Changes

This is the implementation for https://discourse.llvm.org/t/rfc-delay-definition-die-searching-when-parse-a-declaration-die-for-record-type/78526.

Motivation

Currently, lldb eagerly searches for definition DIE when parsing a declaration DIE for struct/class/union definition DIE. It will search for all definition DIEs with the same unqualified name (just DW_AT_name ) and then find out those DIEs with same fully qualified name. Then lldb will try to resolve those DIEs to create the Types from definition DIEs. It works fine most time. However, when built with -gsimple-template-names, the search graph expands very quickly, because for the specialized-template classes, they don’t have template parameter names encoded inside DW_AT_name. They have DW_TAG_template_type_parameter to reference the types used as template parameters. In order to identify if a definition DIE matches a declaration DIE, lldb needs to resolve all template parameter types first and those template parameter types might be template classes as well, and so on… So, the search graph explodes, causing a lot unnecessary searching/type-resolving to just get the fully qualified names for a specialized-template class. This causes lldb stack overflow for us internally on template-heavy libraries.

Implementation

Instead of searching for definition DIEs when parsing declaration DIEs, we always construct the record type from the DIE regardless if it's definition or declaration. At the same time, use a map GetDeclarationDIEToDefinitionDIE() to track the mapping from declarations/definition DIEs to the unique definition DIE. The process of searching for definition DIE is refactored to SymbolFileDWARF::FindDefinitionDIE which is invoked when 1) completing the type on SymbolFileDWARF::CompleteType. 2) the record type needs to start its definition as a containing type so that nested classes can be added into it in PrepareContextToReceiveMembers.

The key difference is SymbolFileDWARF::ResolveType return a Type* that might be created from declaration DIE, which means it hasn't starts its definition yet. We also need to change according in places where we want the type to start definition, like PrepareContextToReceiveMembers (I'm not aware of any other places, but this should be a simple call to SymbolFileDWARF::FindDefinitionDIE)

Result

It fixes the stack overflow of lldb for the internal binary built with simple template name. When constructing the fully qualified name built with -gsimple-template-names, it gets the name of the type parameter by resolving the referenced DIE, which might be a declaration (we won't try to search for the definition DIE to just get the name).


Patch is 34.35 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/90663.diff

7 Files Affected:

  • (modified) lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp (+107-163)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp (+103-2)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h (+14)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.cpp (+5)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARFDwo.h (+2)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/UniqueDWARFASTType.cpp (+50-45)
  • (modified) lldb/source/Plugins/SymbolFile/DWARF/UniqueDWARFASTType.h (+15-6)
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
index bea11e0e3840af..7ad661c9a9d49b 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
@@ -252,7 +252,8 @@ static void ForcefullyCompleteType(CompilerType type) {
 static void PrepareContextToReceiveMembers(TypeSystemClang &ast,
                                            ClangASTImporter &ast_importer,
                                            clang::DeclContext *decl_ctx,
-                                           DWARFDIE die,
+                                           const DWARFDIE &decl_ctx_die,
+                                           const DWARFDIE &die,
                                            const char *type_name_cstr) {
   auto *tag_decl_ctx = clang::dyn_cast<clang::TagDecl>(decl_ctx);
   if (!tag_decl_ctx)
@@ -279,6 +280,13 @@ static void PrepareContextToReceiveMembers(TypeSystemClang &ast,
         type_name_cstr ? type_name_cstr : "", die.GetOffset());
   }
 
+  // By searching for the definition DIE of the decl_ctx type, we will either:
+  // 1. Found the the definition DIE and start its definition with
+  // TypeSystemClang::StartTagDeclarationDefinition.
+  // 2. Unable to find it, then need to forcefully complete it.
+  die.GetDWARF()->FindDefinitionDIE(decl_ctx_die);
+  if (tag_decl_ctx->isCompleteDefinition() || tag_decl_ctx->isBeingDefined())
+    return;
   // We don't have a type definition and/or the import failed. We must
   // forcefully complete the type to avoid crashes.
   ForcefullyCompleteType(type);
@@ -620,10 +628,11 @@ DWARFASTParserClang::ParseTypeModifier(const SymbolContext &sc,
   if (tag == DW_TAG_typedef) {
     // DeclContext will be populated when the clang type is materialized in
     // Type::ResolveCompilerType.
-    PrepareContextToReceiveMembers(
-        m_ast, GetClangASTImporter(),
-        GetClangDeclContextContainingDIE(die, nullptr), die,
-        attrs.name.GetCString());
+    DWARFDIE decl_ctx_die;
+    clang::DeclContext *decl_ctx =
+        GetClangDeclContextContainingDIE(die, &decl_ctx_die);
+    PrepareContextToReceiveMembers(m_ast, GetClangASTImporter(), decl_ctx,
+                                   decl_ctx_die, die, attrs.name.GetCString());
 
     if (attrs.type.IsValid()) {
       // Try to parse a typedef from the (DWARF embedded in the) Clang
@@ -1100,32 +1109,6 @@ DWARFASTParserClang::ParseSubroutine(const DWARFDIE &die,
         // struct and see if this is actually a C++ method
         Type *class_type = dwarf->ResolveType(decl_ctx_die);
         if (class_type) {
-          if (class_type->GetID() != decl_ctx_die.GetID() ||
-              IsClangModuleFwdDecl(decl_ctx_die)) {
-
-            // We uniqued the parent class of this function to another
-            // class so we now need to associate all dies under
-            // "decl_ctx_die" to DIEs in the DIE for "class_type"...
-            DWARFDIE class_type_die = dwarf->GetDIE(class_type->GetID());
-
-            if (class_type_die) {
-              std::vector<DWARFDIE> failures;
-
-              CopyUniqueClassMethodTypes(decl_ctx_die, class_type_die,
-                                         class_type, failures);
-
-              // FIXME do something with these failures that's
-              // smarter than just dropping them on the ground.
-              // Unfortunately classes don't like having stuff added
-              // to them after their definitions are complete...
-
-              Type *type_ptr = dwarf->GetDIEToType()[die.GetDIE()];
-              if (type_ptr && type_ptr != DIE_IS_BEING_PARSED) {
-                return type_ptr->shared_from_this();
-              }
-            }
-          }
-
           if (attrs.specification.IsValid()) {
             // We have a specification which we are going to base our
             // function prototype off of, so we need this type to be
@@ -1260,6 +1243,39 @@ DWARFASTParserClang::ParseSubroutine(const DWARFDIE &die,
               }
             }
           }
+          // By here, we should have already completed the c++ class_type
+          // because if either specification or abstract_origin is present, we
+          // call GetClangDeclContextForDIE to resolve the DW_TAG_subprogram
+          // refered by this one until we reached the DW_TAG_subprogram without
+          // specification or abstract_origin (the else branch above). Then the
+          // above GetFullCompilerType() will complete the class_type if it's
+          // not completed yet. After that, we will have the mapping from DIEs
+          // in class_type_die to DeclContexts in m_die_to_decl_ctx.
+          if (class_type->GetID() != decl_ctx_die.GetID() ||
+              IsClangModuleFwdDecl(decl_ctx_die)) {
+
+            // We uniqued the parent class of this function to another
+            // class so we now need to associate all dies under
+            // "decl_ctx_die" to DIEs in the DIE for "class_type"...
+            DWARFDIE class_type_die = dwarf->GetDIE(class_type->GetID());
+
+            if (class_type_die) {
+              std::vector<DWARFDIE> failures;
+
+              CopyUniqueClassMethodTypes(decl_ctx_die, class_type_die,
+                                         class_type, failures);
+
+              // FIXME do something with these failures that's
+              // smarter than just dropping them on the ground.
+              // Unfortunately classes don't like having stuff added
+              // to them after their definitions are complete...
+
+              Type *type_ptr = dwarf->GetDIEToType()[die.GetDIE()];
+              if (type_ptr && type_ptr != DIE_IS_BEING_PARSED) {
+                return type_ptr->shared_from_this();
+              }
+            }
+          }
         }
       }
     }
@@ -1651,6 +1667,7 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
 
   ConstString unique_typename(attrs.name);
   Declaration unique_decl(attrs.decl);
+  uint64_t byte_size = attrs.byte_size.value_or(0);
 
   if (attrs.name) {
     if (Language::LanguageIsCPlusPlus(cu_language)) {
@@ -1664,13 +1681,34 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
     }
 
     if (dwarf->GetUniqueDWARFASTTypeMap().Find(
-            unique_typename, die, unique_decl, attrs.byte_size.value_or(-1),
-            *unique_ast_entry_up)) {
+            unique_typename, die, unique_decl, byte_size,
+            attrs.is_forward_declaration, *unique_ast_entry_up)) {
       type_sp = unique_ast_entry_up->m_type_sp;
       if (type_sp) {
         dwarf->GetDIEToType()[die.GetDIE()] = type_sp.get();
         LinkDeclContextToDIE(
             GetCachedClangDeclContextForDIE(unique_ast_entry_up->m_die), die);
+        if (!attrs.is_forward_declaration) {
+          dwarf->GetDeclarationDIEToDefinitionDIE().try_emplace(
+              unique_ast_entry_up->m_die.GetDIE(), *die.GetDIERef());
+          // If the parameter DIE is definition and the entry in the map is
+          // declaration, then we need to update the entry to point to the
+          // definition DIE.
+          if (unique_ast_entry_up->m_is_forward_declaration) {
+            unique_ast_entry_up->m_die = die;
+            unique_ast_entry_up->m_byte_size = byte_size;
+            unique_ast_entry_up->m_declaration = unique_decl;
+            unique_ast_entry_up->m_is_forward_declaration = false;
+            // Need to update Type ID to refer to the definition DIE. because
+            // it's used in ParseSubroutine to determine if we need to copy cxx
+            // method types from a declaration DIE to this definition DIE.
+            type_sp->SetID(die.GetID());
+            if (attrs.class_language != eLanguageTypeObjC &&
+                attrs.class_language != eLanguageTypeObjC_plus_plus)
+              TypeSystemClang::StartTagDeclarationDefinition(
+                  type_sp->GetForwardCompilerType());
+          }
+        }
         return type_sp;
       }
     }
@@ -1707,112 +1745,22 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
     attrs.is_forward_declaration = true;
   }
 
-  if (attrs.class_language == eLanguageTypeObjC ||
-      attrs.class_language == eLanguageTypeObjC_plus_plus) {
-    if (!attrs.is_complete_objc_class &&
-        die.Supports_DW_AT_APPLE_objc_complete_type()) {
-      // We have a valid eSymbolTypeObjCClass class symbol whose name
-      // matches the current objective C class that we are trying to find
-      // and this DIE isn't the complete definition (we checked
-      // is_complete_objc_class above and know it is false), so the real
-      // definition is in here somewhere
-      type_sp =
-          dwarf->FindCompleteObjCDefinitionTypeForDIE(die, attrs.name, true);
-
-      if (!type_sp) {
-        SymbolFileDWARFDebugMap *debug_map_symfile =
-            dwarf->GetDebugMapSymfile();
-        if (debug_map_symfile) {
-          // We weren't able to find a full declaration in this DWARF,
-          // see if we have a declaration anywhere else...
-          type_sp = debug_map_symfile->FindCompleteObjCDefinitionTypeForDIE(
-              die, attrs.name, true);
-        }
-      }
-
-      if (type_sp) {
-        if (log) {
-          dwarf->GetObjectFile()->GetModule()->LogMessage(
-              log,
-              "SymbolFileDWARF({0:p}) - {1:x16}: {2} type "
-              "\"{3}\" is an "
-              "incomplete objc type, complete type is {4:x8}",
-              static_cast<void *>(this), die.GetOffset(),
-              DW_TAG_value_to_name(tag), attrs.name.GetCString(),
-              type_sp->GetID());
-        }
-
-        // We found a real definition for this type elsewhere so lets use
-        // it and cache the fact that we found a complete type for this
-        // die
-        dwarf->GetDIEToType()[die.GetDIE()] = type_sp.get();
-        return type_sp;
-      }
-    }
-  }
-
   if (attrs.is_forward_declaration) {
-    // We have a forward declaration to a type and we need to try and
-    // find a full declaration. We look in the current type index just in
-    // case we have a forward declaration followed by an actual
-    // declarations in the DWARF. If this fails, we need to look
-    // elsewhere...
-    if (log) {
-      dwarf->GetObjectFile()->GetModule()->LogMessage(
-          log,
-          "SymbolFileDWARF({0:p}) - {1:x16}: {2} type \"{3}\" is a "
-          "forward declaration, trying to find complete type",
-          static_cast<void *>(this), die.GetOffset(), DW_TAG_value_to_name(tag),
-          attrs.name.GetCString());
-    }
-
     // See if the type comes from a Clang module and if so, track down
     // that type.
     type_sp = ParseTypeFromClangModule(sc, die, log);
     if (type_sp)
       return type_sp;
-
-    // type_sp = FindDefinitionTypeForDIE (dwarf_cu, die,
-    // type_name_const_str);
-    type_sp = dwarf->FindDefinitionTypeForDWARFDeclContext(die);
-
-    if (!type_sp) {
-      SymbolFileDWARFDebugMap *debug_map_symfile = dwarf->GetDebugMapSymfile();
-      if (debug_map_symfile) {
-        // We weren't able to find a full declaration in this DWARF, see
-        // if we have a declaration anywhere else...
-        type_sp = debug_map_symfile->FindDefinitionTypeForDWARFDeclContext(die);
-      }
-    }
-
-    if (type_sp) {
-      if (log) {
-        dwarf->GetObjectFile()->GetModule()->LogMessage(
-            log,
-            "SymbolFileDWARF({0:p}) - {1:x16}: {2} type \"{3}\" is a "
-            "forward declaration, complete type is {4:x8}",
-            static_cast<void *>(this), die.GetOffset(),
-            DW_TAG_value_to_name(tag), attrs.name.GetCString(),
-            type_sp->GetID());
-      }
-
-      // We found a real definition for this type elsewhere so lets use
-      // it and cache the fact that we found a complete type for this die
-      dwarf->GetDIEToType()[die.GetDIE()] = type_sp.get();
-      clang::DeclContext *defn_decl_ctx =
-          GetCachedClangDeclContextForDIE(dwarf->GetDIE(type_sp->GetID()));
-      if (defn_decl_ctx)
-        LinkDeclContextToDIE(defn_decl_ctx, die);
-      return type_sp;
-    }
   }
+
   assert(tag_decl_kind != -1);
   UNUSED_IF_ASSERT_DISABLED(tag_decl_kind);
-  bool clang_type_was_created = false;
-  clang::DeclContext *decl_ctx = GetClangDeclContextContainingDIE(die, nullptr);
+  DWARFDIE decl_ctx_die;
+  clang::DeclContext *decl_ctx =
+      GetClangDeclContextContainingDIE(die, &decl_ctx_die);
 
-  PrepareContextToReceiveMembers(m_ast, GetClangASTImporter(), decl_ctx, die,
-                                 attrs.name.GetCString());
+  PrepareContextToReceiveMembers(m_ast, GetClangASTImporter(), decl_ctx,
+                                 decl_ctx_die, die, attrs.name.GetCString());
 
   if (attrs.accessibility == eAccessNone && decl_ctx) {
     // Check the decl context that contains this class/struct/union. If
@@ -1850,20 +1798,20 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
             tag_decl_kind, template_param_infos);
     clang_type =
         m_ast.CreateClassTemplateSpecializationType(class_specialization_decl);
-    clang_type_was_created = true;
 
     m_ast.SetMetadata(class_template_decl, metadata);
     m_ast.SetMetadata(class_specialization_decl, metadata);
   }
 
-  if (!clang_type_was_created) {
-    clang_type_was_created = true;
+  if (!clang_type) {
     clang_type = m_ast.CreateRecordType(
         decl_ctx, GetOwningClangModule(die), attrs.accessibility,
         attrs.name.GetCString(), tag_decl_kind, attrs.class_language, &metadata,
         attrs.exports_symbols);
   }
-
+  if (!attrs.is_forward_declaration)
+    dwarf->GetDeclarationDIEToDefinitionDIE().try_emplace(die.GetDIE(),
+                                                          *die.GetDIERef());
   // Store a forward declaration to this class type in case any
   // parameters in any class methods need it for the clang types for
   // function prototypes.
@@ -1880,7 +1828,8 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
   unique_ast_entry_up->m_type_sp = type_sp;
   unique_ast_entry_up->m_die = die;
   unique_ast_entry_up->m_declaration = unique_decl;
-  unique_ast_entry_up->m_byte_size = attrs.byte_size.value_or(0);
+  unique_ast_entry_up->m_byte_size = byte_size;
+  unique_ast_entry_up->m_is_forward_declaration = attrs.is_forward_declaration;
   dwarf->GetUniqueDWARFASTTypeMap().Insert(unique_typename,
                                            *unique_ast_entry_up);
 
@@ -1921,38 +1870,33 @@ DWARFASTParserClang::ParseStructureLikeDIE(const SymbolContext &sc,
           GetClangASTImporter().SetRecordLayout(record_decl, layout);
         }
       }
-    } else if (clang_type_was_created) {
-      // Start the definition if the class is not objective C since the
-      // underlying decls respond to isCompleteDefinition(). Objective
-      // C decls don't respond to isCompleteDefinition() so we can't
-      // start the declaration definition right away. For C++
-      // class/union/structs we want to start the definition in case the
-      // class is needed as the declaration context for a contained class
-      // or type without the need to complete that type..
-
-      if (attrs.class_language != eLanguageTypeObjC &&
-          attrs.class_language != eLanguageTypeObjC_plus_plus)
-        TypeSystemClang::StartTagDeclarationDefinition(clang_type);
-
-      // Leave this as a forward declaration until we need to know the
-      // details of the type. lldb_private::Type will automatically call
-      // the SymbolFile virtual function
-      // "SymbolFileDWARF::CompleteType(Type *)" When the definition
-      // needs to be defined.
-      assert(!dwarf->GetForwardDeclCompilerTypeToDIE().count(
-                 ClangUtil::RemoveFastQualifiers(clang_type)
-                     .GetOpaqueQualType()) &&
-             "Type already in the forward declaration map!");
-      // Can't assume m_ast.GetSymbolFile() is actually a
-      // SymbolFileDWARF, it can be a SymbolFileDWARFDebugMap for Apple
-      // binaries.
-      dwarf->GetForwardDeclCompilerTypeToDIE().try_emplace(
-          ClangUtil::RemoveFastQualifiers(clang_type).GetOpaqueQualType(),
-          *die.GetDIERef());
-      m_ast.SetHasExternalStorage(clang_type.GetOpaqueQualType(), true);
     }
+    // Start the definition if the class is not objective C since the
+    // underlying decls respond to isCompleteDefinition(). Objective
+    // C decls don't respond to isCompleteDefinition() so we can't
+    // start the declaration definition right away. For C++
+    // class/union/structs we want to start the definition in case the
+    // class is needed as the declaration context for a contained class
+    // or type without the need to complete that type..
+
+    if (attrs.class_language != eLanguageTypeObjC &&
+        attrs.class_language != eLanguageTypeObjC_plus_plus)
+      TypeSystemClang::StartTagDeclarationDefinition(clang_type);
   }
 
+  // Leave this as a forward declaration until we need to know the
+  // details of the type. lldb_private::Type will automatically call
+  // the SymbolFile virtual function
+  // "SymbolFileDWARF::CompleteType(Type *)" When the definition
+  // needs to be defined.
+  assert(!dwarf->GetForwardDeclCompilerTypeToDIE().count(
+             ClangUtil::RemoveFastQualifiers(clang_type).GetOpaqueQualType()) &&
+         "Type already in the forward declaration map!");
+  dwarf->GetForwardDeclCompilerTypeToDIE().try_emplace(
+      ClangUtil::RemoveFastQualifiers(clang_type).GetOpaqueQualType(),
+      *die.GetDIERef());
+  m_ast.SetHasExternalStorage(clang_type.GetOpaqueQualType(), true);
+
   // If we made a clang type, set the trivial abi if applicable: We only
   // do this for pass by value - which implies the Trivial ABI. There
   // isn't a way to assert that something that would normally be pass by
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
index 49f13d2c89e380..5a317db7e74028 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp
@@ -1631,13 +1631,19 @@ bool SymbolFileDWARF::CompleteType(CompilerType &compiler_type) {
     return true;
   }
 
-  DWARFDIE dwarf_die = GetDIE(die_it->getSecond());
+  DWARFDIE dwarf_die = FindDefinitionDIE(GetDIE(die_it->getSecond()));
   if (dwarf_die) {
     // Once we start resolving this type, remove it from the forward
     // declaration map in case anyone child members or other types require this
     // type to get resolved. The type will get resolved when all of the calls
     // to SymbolFileDWARF::ResolveClangOpaqueTypeDefinition are done.
-    GetForwardDeclCompilerTypeToDIE().erase(die_it);
+    // Need to get a new iterator because FindDefinitionDIE might add new
+    // entries into the map and invalidate previous iterator.
+    auto die_it = GetForwardDeclCompilerTypeToDIE().find(
+        compiler_type_no_qualifiers.GetOpaqueQualType());
+    if (die_it != GetForwardDeclCompilerTypeToDIE().end()) {
+      GetForwardDeclCompilerTypeToDIE().erase(die_it);
+    }
 
     Type *type = GetDIEToType().lookup(dwarf_die.GetDIE());
 
@@ -1654,6 +1660,101 @@ bool SymbolFileDWARF::CompleteType(CompilerType &compiler_type) {
   return false;
 }
 
+DWARFDIE SymbolFileDWARF::FindDefinitionDIE(const DWARFDIE &die) {
+  auto def_die_it = GetDeclarationDIEToDefinitionDIE().find(die.GetDIE());
+  if (def_die_it != GetDeclarationDIEToDefinitionDIE().end())
+    return GetDIE(def_die_it->getSecond());
+
+  ParsedDWARFTypeAttributes attrs(die);
+  const dw_tag_t tag = die.Tag();
+  TypeSP type_sp;
+  Log *log = GetLog(DWARFLog::TypeCompletion | DWARFLog::Lookups);
+  if (log) {
+    GetObjectFile()->GetModule()->LogMessage(
+        log,
+        "SymbolFileDWARF({0:p}) - {1:x16}: {2} type \"{3}\" is a "
+        "forward declaration DIE, trying to find definition DIE",
+        static_cast<void *>(this), die.GetOffset(), DW_TAG_value_to_name(tag),
+        attrs.name.GetCString());
+  }
+  // We haven't parse definition die for this type, starting to search for it.
+  // After we found the definition die, the GetDeclarationDIEToDefinitionDIE()...
[truncated]

Copy link

github-actions bot commented Apr 30, 2024

✅ With the latest revision this PR passed the C/C++ code formatter.

@dwblaikie
Copy link
Collaborator

does this cause multiple (an open ended amount?) of declarations for a type to be created if the type declarations from multiple CUs are encountered separately? Or still only one due to the extra map?

@ZequanWu
Copy link
Contributor Author

ZequanWu commented May 1, 2024

does this cause multiple (an open ended amount?) of declarations for a type to be created if the type declarations from multiple CUs are encountered separately? Or still only one due to the extra map?

This only creates one even if type declarations are from different CUs. The definition DIE lookup mechanism is the same as before, via manual index, which is able to search cross CUs. We check if a type is created before using the UniqueDWARFASTTypeMap as before: For C++, unique type is identified by full qualified name + byte size. For other languages, it's base name + byte size + declaration location.

@dwblaikie
Copy link
Collaborator

Hmm - but the byte size wouldn't be known from only a declaration, right? so how'd that key work between a declaration and a definition?

@ZequanWu
Copy link
Contributor Author

ZequanWu commented May 1, 2024

Hmm - but the byte size wouldn't be known from only a declaration, right? so how'd that key work between a declaration and a definition?

For declaration and definition DIEs, we just look at the name. Byte size and declaration locations are only used as extra info to differentiate two definition DIEs. See the comment in: https://github.com/llvm/llvm-project/pull/90663/files#diff-82e596d532f38e5212da4007f8ffda731ac8c948ab98eaac21f30fc96ca62d30R24-R32

if (dwarf_die) {
// Once we start resolving this type, remove it from the forward
// declaration map in case anyone child members or other types require this
// type to get resolved. The type will get resolved when all of the calls
// to SymbolFileDWARF::ResolveClangOpaqueTypeDefinition are done.
GetForwardDeclCompilerTypeToDIE().erase(die_it);
// Need to get a new iterator because FindDefinitionDIE might add new
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to erase the iterator before calling FindDefinitionDIE?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, we change m_forward_decl_compiler_type_to_die to be updated with definition DIE. So, we may need to erase twice, because the first erase is always necessary if we failed to find definition DIE for it. The second erase is because calling FindDefinitionDIE might add new entry to the definition DIE because the first one removed it.

CompilerTypeToDIE m_forward_decl_compiler_type_to_die;
// A map from a struct/class/union/enum DIE (might be a declaration or a
// definition) to its definition DIE.
DIEToDIE m_die_to_def_die;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to avoid creating the extra map, for example by modifying the m_forward_decl_compiler_type_to_die to point to the definition DIE -- after that DIE has been located?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I also can't help but wonder why do we need that m_forward_decl_compiler_type_to_die in the first place, given that the ClangASTMetadata associated with the type already contains a user ID, which should allow us to retrieve original DIE. I know this isn't related to your patch, I'm just writing it in case you or someone has any thoughts on this.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.cpp Outdated Show resolved Hide resolved
Copy link
Collaborator

@labath labath left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modulo comments, this makes sense to me (as much as that can ever be said about this code), but it could definitely use a second (third?) pair of eyes. Michael, what do you make of this?

DWARFDIE die,
const char *type_name_cstr) {
void DWARFASTParserClang::PrepareContextToReceiveMembers(
TypeSystemClang &ast, clang::DeclContext *decl_ctx,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This arg is not necessary now that this is a member

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// Return true if we found the definition DIE for it. is_forward_declaration
// is set to true if the parameter die is a declaration.
virtual bool
FindDefinitionDIE(const lldb_private::plugin::dwarf::DWARFDIE &die,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete lldb_private::plugin::dwarf::

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

// Leave this as a forward declaration until we need to know the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having trouble reconciling this comment with the one above (line 1974). How can we leave this as a forward declaration if we have already (conditionally, if we are processing a definition DIE of a non-objc type) started its definition (line 1984). What exactly is this trying to say?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't modify the original comments. It doesn't make sense to me as well. I append "If this is a declaration DIE" in front of this comment.

@@ -108,6 +108,9 @@ class DWARFASTParserClang : public lldb_private::plugin::dwarf::DWARFASTParser {
lldb_private::ConstString GetDIEClassTemplateParams(
const lldb_private::plugin::dwarf::DWARFDIE &die) override;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

delete lldb_private::plugin::dwarf:: (and elsewhere in this file)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


if (attrs.class_language != eLanguageTypeObjC &&
attrs.class_language != eLanguageTypeObjC_plus_plus)
TypeSystemClang::StartTagDeclarationDefinition(clang_type);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it a problem that clang_type can already have its definition started (and completed) on line 1944 (if the DIE has no children)? Should this maybe be in some sort of an else branch?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't notice it. Moved this into an else branch.

@@ -1632,6 +1669,96 @@ DWARFASTParserClang::GetCPlusPlusQualifiedName(const DWARFDIE &die) {
return qualified_name;
}

bool DWARFASTParserClang::FindDefinitionDIE(const DWARFDIE &die,
bool &is_forward_declaration) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm having trouble finding where is this by-ref argument actually used in the caller. Can we get rid of it (perhaps by moving the IsForwardDeclaration call to the caller)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was about to use it in SymbolFileDWARF::CompleteType: https://github.com/llvm/llvm-project/pull/90663/files#diff-edef3a65d5d569bbb75a4158d35b827aa5d42ee03ccd3b0c1d4f354afa12210cR1645. But with some refactor change, it's no longer useful anymore.

Removed.

Copy link
Member

@Michael137 Michael137 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea makes sense and I like that we could factor things out of ParseStructureLikeDIE, so generally LGTM (module Pavel's comments). Is any of it testable?

: (udt.m_byte_size < 0 || byte_size < 0 ||
udt.m_byte_size == byte_size) &&
udt.m_declaration == decl;
if (matching_size_declaration) {
Copy link
Member

@Michael137 Michael137 May 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For readability, can we do:

if (!matching_size_declaration)
  continue;

?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

type_sp = unique_ast_entry_up->m_type_sp;
if (type_sp) {
dwarf->GetDIEToType()[die.GetDIE()] = type_sp.get();
LinkDeclContextToDIE(
GetCachedClangDeclContextForDIE(unique_ast_entry_up->m_die), die);
if (!attrs.is_forward_declaration) {
// If the parameter DIE is definition and the entry in the map is
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find parameter DIE a bit confusing. Made me think of templates or functions. Could we just reference the parameter, e.g., If the 'die' being parsed is a definition ... or something like that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@labath
Copy link
Collaborator

labath commented May 3, 2024

Is any of it testable?

Good question. Though this is mostly meant to be "NFC" (with very large quotes), I can imagine us doing something like forcing the parsing of a specific type (type lookup <something> ?), and then checking that the module ast (image dump ast) does not contain specific types -- as that's basically what we're trying to achieve.

@Michael137
Copy link
Member

Is any of it testable?

Good question. Though this is mostly meant to be "NFC" (with very large quotes), I can imagine us doing something like forcing the parsing of a specific type (type lookup <something> ?), and then checking that the module ast (image dump ast) does not contain specific types -- as that's basically what we're trying to achieve.

Yea that could work. But if it's going to be very painful or fragile to test then don't let that hold back the PR

Copy link
Contributor Author

@ZequanWu ZequanWu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though this is mostly meant to be "NFC" (with very large quotes)

Yeah, this is mostly "NFC". A noticeable difference is we now set the type created from declaration with TypeSystemClang::SetHasExternalStorage without knowing if there's a definition or not. Based on my knowledge, it simply tells clang that the type can be completed by external AST source and ultimately calls SymbolFileDWARF::CompleteType to complete it. If there isn't one, we just fail complete it. Maybe we should also force completion of the type in this case?

Update:
Actually the behaviour of forced type completion is remained the same: Only force containing type completion when trying to parse a contained type and the containing type has no definition.

DWARFDIE die,
const char *type_name_cstr) {
void DWARFASTParserClang::PrepareContextToReceiveMembers(
TypeSystemClang &ast, clang::DeclContext *decl_ctx,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

// Return true if we found the definition DIE for it. is_forward_declaration
// is set to true if the parameter die is a declaration.
virtual bool
FindDefinitionDIE(const lldb_private::plugin::dwarf::DWARFDIE &die,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -1632,6 +1669,96 @@ DWARFASTParserClang::GetCPlusPlusQualifiedName(const DWARFDIE &die) {
return qualified_name;
}

bool DWARFASTParserClang::FindDefinitionDIE(const DWARFDIE &die,
bool &is_forward_declaration) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was about to use it in SymbolFileDWARF::CompleteType: https://github.com/llvm/llvm-project/pull/90663/files#diff-edef3a65d5d569bbb75a4158d35b827aa5d42ee03ccd3b0c1d4f354afa12210cR1645. But with some refactor change, it's no longer useful anymore.

Removed.


if (attrs.class_language != eLanguageTypeObjC &&
attrs.class_language != eLanguageTypeObjC_plus_plus)
TypeSystemClang::StartTagDeclarationDefinition(clang_type);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I didn't notice it. Moved this into an else branch.

}

// Leave this as a forward declaration until we need to know the
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't modify the original comments. It doesn't make sense to me as well. I append "If this is a declaration DIE" in front of this comment.

@@ -108,6 +108,9 @@ class DWARFASTParserClang : public lldb_private::plugin::dwarf::DWARFASTParser {
lldb_private::ConstString GetDIEClassTemplateParams(
const lldb_private::plugin::dwarf::DWARFDIE &die) override;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

: (udt.m_byte_size < 0 || byte_size < 0 ||
udt.m_byte_size == byte_size) &&
udt.m_declaration == decl;
if (matching_size_declaration) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

type_sp = unique_ast_entry_up->m_type_sp;
if (type_sp) {
dwarf->GetDIEToType()[die.GetDIE()] = type_sp.get();
LinkDeclContextToDIE(
GetCachedClangDeclContextForDIE(unique_ast_entry_up->m_die), die);
if (!attrs.is_forward_declaration) {
// If the parameter DIE is definition and the entry in the map is
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@ZequanWu
Copy link
Contributor Author

ZequanWu commented May 3, 2024

Is any of it testable?

Good question. Though this is mostly meant to be "NFC" (with very large quotes), I can imagine us doing something like forcing the parsing of a specific type (type lookup <something> ?), and then checking that the module ast (image dump ast) does not contain specific types -- as that's basically what we're trying to achieve.

Yea that could work. But if it's going to be very painful or fragile to test then don't let that hold back the PR

In terms of testing, since this only delays definition DIE searching not type completion, we need to construct a test so that lldb finds the declaration DIE first without trigger a type completion on it and somehow test the incomplete type. The first part is tricky. I'm not sure how to achieve it.

Copy link
Collaborator

@clayborg clayborg left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks pretty good to me as long as the test suite is happy.

@clayborg
Copy link
Collaborator

clayborg commented May 3, 2024

Is any of it testable?

Good question. Though this is mostly meant to be "NFC" (with very large quotes), I can imagine us doing something like forcing the parsing of a specific type (type lookup <something> ?), and then checking that the module ast (image dump ast) does not contain specific types -- as that's basically what we're trying to achieve.

Yea that could work. But if it's going to be very painful or fragile to test then don't let that hold back the PR

In terms of testing, since this only delays definition DIE searching not type completion, we need to construct a test so that lldb finds the declaration DIE first without trigger a type completion on it and somehow test the incomplete type. The first part is tricky. I'm not sure how to achieve it.

You should be able to make a test case with two files. One file contains the class definition and the other uses only a forward declaration. You could test by running the binary stopping only in the one with the forward declaration. So file 1 would be something like foo.cpp containing

struct Foo { 
  int value;
  Foo(int v): value(v) {}
};

Foo *CreateFoo() {
  return new Foo(1);
}

Then having main.cpp contain:

struct Foo; // Forward declare Foo
// Prototypes that don't need Foo definition
Foo *CreateFoo();

int main() {
  Foo *foo = CreateFoo();
  return 0; // Breakpoint 1 here
}

Then run to "Breakpoint 1 here" and then run frame variable foo. Foo won't need to be completed for this. But if you then run frame variable *foo, it should cause the type to be completed and show the instance variable correctly.

@ZequanWu
Copy link
Contributor Author

ZequanWu commented May 3, 2024

Is any of it testable?

Good question. Though this is mostly meant to be "NFC" (with very large quotes), I can imagine us doing something like forcing the parsing of a specific type (type lookup <something> ?), and then checking that the module ast (image dump ast) does not contain specific types -- as that's basically what we're trying to achieve.

Yea that could work. But if it's going to be very painful or fragile to test then don't let that hold back the PR

In terms of testing, since this only delays definition DIE searching not type completion, we need to construct a test so that lldb finds the declaration DIE first without trigger a type completion on it and somehow test the incomplete type. The first part is tricky. I'm not sure how to achieve it.

You should be able to make a test case with two files. One file contains the class definition and the other uses only a forward declaration. You could test by running the binary stopping only in the one with the forward declaration. So file 1 would be something like foo.cpp containing

struct Foo { 
  int value;
  Foo(int v): value(v) {}
};

Foo *CreateFoo() {
  return new Foo(1);
}

Then having main.cpp contain:

struct Foo; // Forward declare Foo
// Prototypes that don't need Foo definition
Foo *CreateFoo();

int main() {
  Foo *foo = CreateFoo();
  return 0; // Breakpoint 1 here
}

Then run to "Breakpoint 1 here" and then run frame variable foo. Foo won't need to be completed for this. But if you then run frame variable *foo, it should cause the type to be completed and show the instance variable correctly.

The tests your described testing this change doesn't break things by delaying definition DIE searching, which I think is already covered by existing tests (created for other purposes, but also covers this case). I was thinking about testing the definition DIE searching is actually delayed. Maybe there isn't a way to do it.

@clayborg
Copy link
Collaborator

clayborg commented May 3, 2024

The tests your described testing this change doesn't break things by delaying definition DIE searching, which I think is already covered by existing tests (created for other purposes, but also covers this case). I was thinking about testing the definition DIE searching is actually delayed. Maybe there isn't a way to do it.

You could enable logging and check for specific logging after steps. In the test I described above if you just print the "Foo *foo" variable, it won't need to complete the definition, you could check for logging, and then if you print "*foo", then it should complete the definition and you should see logging. Not sure if that is what you meant

@adrian-prantl
Copy link
Collaborator

@ZequanWu in the future, if one of your commits break a bot, make sure to revert it immediately, you can always re-land it later with a fix or an explanation why it wasn't your commit that broke the bots. Reverting a commit is cheap, red bots are expensive :-)

@ZequanWu
Copy link
Contributor Author

I was able to reproduce the failure of these three:

lldb-api :: lang/c/forward/TestForwardDeclaration.py
lldb-api :: lang/cpp/unique-types3/TestUniqueTypes3.py
lldb-api :: types/TestRecursiveTypes.py

locally. Reverting this patch and a7eff59 which depended on it made the failure go away.

I reverted the patches for now to get the bots clean.

Thanks. Can you provide instructions to repro the failure locally?

@ZequanWu in the future, if one of your commits break a bot, make sure to revert it immediately, you can always re-land it later with a fix or an explanation why it wasn't your commit that broke the bots. Reverting a commit is cheap, red bots are expensive :-)

Will do.

@jimingham
Copy link
Collaborator

jimingham commented May 14, 2024 via email

@jimingham
Copy link
Collaborator

jimingham commented May 14, 2024 via email

@ZequanWu
Copy link
Contributor Author

your commit deleted that file I think, I added it back when I did the revert (possibly a mistake)... It passes on my macOS system but is failing on Ubuntu after the revert. I think I'll just disable it for now.

This change adds the new test, so deleting it as part of the reverting is expected.

@jimingham
Copy link
Collaborator

jimingham commented May 14, 2024 via email

@ZequanWu
Copy link
Contributor Author

ZequanWu commented May 14, 2024

Reverting those two commits seems to have caused this build failure on Ubuntu:

You forgot to delete the newly added test SymbolFile/DWARF/delayed-definition-die-searching.test in the reverting: 37b8e5f

@jimingham
Copy link
Collaborator

jimingham commented May 14, 2024 via email

@ZequanWu
Copy link
Contributor Author

Can you take care of cleaning this up, this seems like a slightly complex patch and not in an area I'm familiar with.

Yes, will do. Sorry for the mess without reverting it earlier.

@jimingham
Copy link
Collaborator

jimingham commented May 14, 2024 via email

@adrian-prantl
Copy link
Collaborator

Thanks. Can you provide instructions to repro the failure locally?

The bot log should have the cmake line and all the commands that were run there.

@ZequanWu
Copy link
Contributor Author

ZequanWu commented May 15, 2024

I had a fix to this: Let SymbolFileDWARF::GetForwardDeclCompilerTypeToDIE do the same as SymbolFileDWARF::GetUniqueDWARFASTTypeMap: inquery SymbolFileDWARFDebugMap first to get the shared underlying SymbolFile so the map is shared among multiple SymbolFileDWARF. It's here: ZequanWu@2172c66

It fixes those failed tests shown on the macos bot. However, I noticed that lldb crashes on three tests related to clang module (they also crashes when the fix is not given, but not crash after reverting this PR):

Unresolved Tests (3):
  lldb-api :: commands/expression/import-std-module/deque-dbg-info-content/TestDbgInfoContentDequeFromStdModule.py
  lldb-api :: commands/expression/import-std-module/list-dbg-info-content/TestDbgInfoContentListFromStdModule.py
  lldb-api :: commands/expression/import-std-module/vector-dbg-info-content/TestDbgInfoContentVectorFromStdModule.py

I found it's the following commands causing crash.

$ out/cmake/bin/lldb out/cmake/lldb-test-build.noindex/commands/expression/import-std-module/vector-dbg-info-content/TestDbgInfoContentVectorFromStdModule.test_dwarf/a.out -o "settings set symbols.clang-modules-cache-path /Users/zequanwu/work/llvm/out/cmake/lldb-test-build.noindex/module-cache-lldb/lldb-api" -o "settings set target.import-std-module true" -o "b 9" -o "r"  -o "expr a"
(lldb) target create "../cmake/lldb-test-build.noindex/commands/expression/import-std-module/vector-dbg-info-content/TestDbgInfoContentVectorFromStdModule.test_dwarf/a.out"
Current executable set to '/Users/zequanwu/work/llvm/out/cmake/lldb-test-build.noindex/commands/expression/import-std-module/vector-dbg-info-content/TestDbgInfoContentVectorFromStdModule.test_dwarf/a.out' (arm64).
(lldb) settings set symbols.clang-modules-cache-path /Users/zequanwu/work/llvm/out/cmake/lldb-test-build.noindex/module-cache-lldb/lldb-api
(lldb) settings set target.import-std-module true
(lldb) b 9
Breakpoint 1: where = a.out`main + 104 at main.cpp:9:3, address = 0x0000000100002508
(lldb) r
Process 12273 launched: '/Users/zequanwu/work/llvm/out/cmake/lldb-test-build.noindex/commands/expression/import-std-module/vector-dbg-info-content/TestDbgInfoContentVectorFromStdModule.test_dwarf/a.out' (arm64)
Process 12273 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x0000000100002508 a.out`main(argc=1, argv=0x000000016fdff428) at main.cpp:9:3
   6
   7    int main(int argc, char **argv) {
   8      std::vector<Foo> a = {{3}, {1}, {2}};
-> 9      return 0; // Set break point at this line.
   10   }
(lldb) expr a
Assertion failed: (0 && "Invalid SLocOffset or bad function choice"), function getFileIDLoaded, file SourceManager.cpp, line 865.
LLDB diagnostics will be written to /var/folders/jf/zylbx28s05n0d_xwqdf5jnrc00lnhs/T/diagnostics-512963
Please include the directory content when filing a bug report
[1]    12267 abort      bin/lldb  -o  -o "settings set target.import-std-module true" -o "b 9" -o "r"

The clang module in out/cmake/lldb-test-build.noindex/module-cache-lldb/lldb-api was created when running check-lldb. If I delete the clang modules in that directory and run the above command again, it no longer crashes and able to give correct result (after the first run, a new clang module is created in the directory. Following runs of the above commands no longer crashes). So, it looks like related to stale clang module. If I use debug built lldb, it no longer crashes. Do you have any idea how to debug this crash? I'm not familiar with how clang module interact with type completion etc.

Update:
I also added some logging in DWARFASTParserClang::ParseTypeFromClangModule after clang_module_sp is found, but the logging is not showing up before the crash.

@ZequanWu
Copy link
Contributor Author

ZequanWu commented May 15, 2024

It actually still crashes at the same place even without this PR using the following command, you can try it on trunk:

$ rm -rf out/cmake/lldb-test-build.noindex/module-cache-lldb/lldb-api/*
$ out/cmake/bin/lldb-dotest --lldb-module-cache-dir=out/cmake/lldb-test-build.noindex/module-cache-lldb/lldb-api lldb/test/API/commands/expression/import-std-module/
/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.9/bin/python3.9 /Users/zequanwu/work/llvm/lldb/test/API/dotest.py --arch arm64 --build-dir /Users/zequanwu/work/llvm/out/cmake/lldb-test-build.noindex --executable /Users/zequanwu/work/llvm/out/cmake/./bin/lldb --compiler /Users/zequanwu/work/llvm/out/cmake/./bin/clang --dsymutil /Users/zequanwu/work/llvm/out/cmake/./bin/dsymutil --lldb-libs-dir /Users/zequanwu/work/llvm/out/cmake/./lib --llvm-tools-dir /Users/zequanwu/work/llvm/out/cmake/./bin --libcxx-include-dir /Users/zequanwu/work/llvm/out/cmake/include/c++/v1 --libcxx-library-dir /Users/zequanwu/work/llvm/out/cmake/lib --lldb-obj-root /Users/zequanwu/work/llvm/out/cmake/tools/lldb --lldb-module-cache-dir=/Users/zequanwu/work/llvm/out/cmake/lldb-test-build.noindex/module-cache-lldb/lldb-api lldb/test/API/commands/expression/import-std-module/
lldb version 19.0.0git (git@github.com:ZequanWu/llvm-project.git revision 71fbbb69d63c461f391cbabf1e32cd9977c4ce68)
  clang revision 71fbbb69d63c461f391cbabf1e32cd9977c4ce68
  llvm revision 71fbbb69d63c461f391cbabf1e32cd9977c4ce68
Skipping the following test categories: ['libstdcxx', 'dwo', 'llgs', 'fork']
PASS: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dsym (TestDbgInfoContentForwardListFromStdModule.TestDbgInfoContentForwardList)
PASS: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dwarf (TestDbgInfoContentForwardListFromStdModule.TestDbgInfoContentForwardList)
UNSUPPORTED: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dwo (TestDbgInfoContentForwardListFromStdModule.TestDbgInfoContentForwardList) (test case does not fall in any category of interest for this run)
PASS: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dsym (TestForwardListFromStdModule.TestBasicForwardList)
PASS: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dwarf (TestForwardListFromStdModule.TestBasicForwardList)
UNSUPPORTED: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dwo (TestForwardListFromStdModule.TestBasicForwardList) (test case does not fall in any category of interest for this run)
PASS: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dsym (TestRetryWithStdModule.TestCase)
PASS: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dwarf (TestRetryWithStdModule.TestCase)
UNSUPPORTED: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dwo (TestRetryWithStdModule.TestCase) (test case does not fall in any category of interest for this run)
PASS: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dsym (TestSharedPtrFromStdModule.TestSharedPtr)
PASS: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dwarf (TestSharedPtrFromStdModule.TestSharedPtr)
UNSUPPORTED: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dwo (TestSharedPtrFromStdModule.TestSharedPtr) (test case does not fall in any category of interest for this run)
PASS: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dsym (TestEmptyStdModule.ImportStdModule)
PASS: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dwarf (TestEmptyStdModule.ImportStdModule)
UNSUPPORTED: LLDB (/Users/zequanwu/work/llvm/out/cmake/bin/clang-arm64) :: test_dwo (TestEmptyStdModule.ImportStdModule) (test case does not fall in any category of interest for this run)
Assertion failed: (0 && "Invalid SLocOffset or bad function choice"), function getFileIDLoaded, file SourceManager.cpp, line 865.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.

But I don't know why this change will make this crash explicitly shows up when running check-lldb.

Michael137 added a commit to Michael137/llvm-project that referenced this pull request May 23, 2024
…ng when parsing declaration DIEs. (llvm#90663)"

This reverts commit 9a7262c.
ZequanWu added a commit that referenced this pull request May 28, 2024
…ing when parsing declaration DIEs. (#92328)

This reapplies
9a7262c
(#90663) and added #91808 as a
fix.

It was causing tests on macos to fail because
`SymbolFileDWARF::GetForwardDeclCompilerTypeToDIE` returned the map
owned by this symol file. When there were two symbol files, two
different maps were created for caching from compiler type to DIE even
if they are for the same module. The solution is to do the same as
`SymbolFileDWARF::GetUniqueDWARFASTTypeMap`: inquery
SymbolFileDWARFDebugMap first to get the shared underlying SymbolFile so
the map is shared among multiple SymbolFileDWARF.
vg0204 pushed a commit to vg0204/llvm-project that referenced this pull request May 29, 2024
…ing when parsing declaration DIEs. (llvm#92328)

This reapplies
llvm@9a7262c
(llvm#90663) and added llvm#91808 as a
fix.

It was causing tests on macos to fail because
`SymbolFileDWARF::GetForwardDeclCompilerTypeToDIE` returned the map
owned by this symol file. When there were two symbol files, two
different maps were created for caching from compiler type to DIE even
if they are for the same module. The solution is to do the same as
`SymbolFileDWARF::GetUniqueDWARFASTTypeMap`: inquery
SymbolFileDWARFDebugMap first to get the shared underlying SymbolFile so
the map is shared among multiple SymbolFileDWARF.
labath added a commit to labath/llvm-project that referenced this pull request Jun 24, 2024
If ParseStructureLikeDIE (or ParseEnum) encountered a declaration DIE,
it would call FindDefinitionTypeForDIE. This returned a fully formed
type, which it achieved by recursing back into ParseStructureLikeDIE
with the definition DIE.

This obscured the control flow and caused us to repeat some work (e.g.
the UniqueDWARFASTTypeMap lookup), but it mostly worked until we tried
to delay the definition search in llvm#90663. After this patch, the two
ParseStructureLikeDIE calls were no longer recursive, but rather
the second call happened as a part of the CompleteType() call. This
opened the door to inconsistencies, as the second ParseStructureLikeDIE
call was not aware it was called to process a definition die for an
existing type.

To make that possible, this patch removes the recusive type resolution
from this function, and leaves just the "find definition die"
functionality. After finding the definition DIE, we just go back to the
original ParseStructureLikeDIE call, and have it finish the parsing
process with the new DIE.

While this patch is motivated by the work on delaying the definition
searching, I believe it is also useful on its own.
labath added a commit that referenced this pull request Jun 25, 2024
…IEs (#96484)

If ParseStructureLikeDIE (or ParseEnum) encountered a declaration DIE,
it would call FindDefinitionTypeForDIE. This returned a fully formed
type, which it achieved by recursing back into ParseStructureLikeDIE
with the definition DIE.

This obscured the control flow and caused us to repeat some work (e.g.
the UniqueDWARFASTTypeMap lookup), but it mostly worked until we tried
to delay the definition search in #90663. After this patch, the two
ParseStructureLikeDIE calls were no longer recursive, but rather the
second call happened as a part of the CompleteType() call. This opened
the door to inconsistencies, as the second ParseStructureLikeDIE call
was not aware it was called to process a definition die for an existing
type.

To make that possible, this patch removes the recusive type resolution
from this function, and leaves just the "find definition die"
functionality. After finding the definition DIE, we just go back to the
original ParseStructureLikeDIE call, and have it finish the parsing
process with the new DIE.

While this patch is motivated by the work on delaying the definition
searching, I believe it is also useful on its own.
AlexisPerry pushed a commit to llvm-project-tlp/llvm-project that referenced this pull request Jul 9, 2024
…IEs (llvm#96484)

If ParseStructureLikeDIE (or ParseEnum) encountered a declaration DIE,
it would call FindDefinitionTypeForDIE. This returned a fully formed
type, which it achieved by recursing back into ParseStructureLikeDIE
with the definition DIE.

This obscured the control flow and caused us to repeat some work (e.g.
the UniqueDWARFASTTypeMap lookup), but it mostly worked until we tried
to delay the definition search in llvm#90663. After this patch, the two
ParseStructureLikeDIE calls were no longer recursive, but rather the
second call happened as a part of the CompleteType() call. This opened
the door to inconsistencies, as the second ParseStructureLikeDIE call
was not aware it was called to process a definition die for an existing
type.

To make that possible, this patch removes the recusive type resolution
from this function, and leaves just the "find definition die"
functionality. After finding the definition DIE, we just go back to the
original ParseStructureLikeDIE call, and have it finish the parsing
process with the new DIE.

While this patch is motivated by the work on delaying the definition
searching, I believe it is also useful on its own.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants